package com.lagou.edu.factory;

import com.lagou.edu.annotation.Autowired;
import com.lagou.edu.annotation.Service;
import com.lagou.edu.annotation.Transactional;
import com.lagou.edu.utils.ComponentScanUtils;
import com.mysql.jdbc.StringUtils;
import org.dom4j.Document;
import org.dom4j.Element;
import org.dom4j.io.SAXReader;

import java.io.InputStream;
import java.lang.reflect.Field;
import java.lang.reflect.Method;
import java.util.*;

/**
 * @author 应癫
 *
 * 工厂类，生产对象（使用反射技术）
 */
public class BeanFactory {

    /**
     * 任务一：读取解析xml，通过反射技术实例化对象并且存储待用（map集合）
     * 任务二：对外提供获取实例对象的接口（根据id获取）
     */

    private static Map<String,Object> map = new HashMap<>();  // 存储对象

    private static Map<String, Set<String>> autowiredMap = new HashMap<>();  // 存放普通依赖bean,key为被依赖的beanid,value为依赖的bean name set

    private static Set<String> proxySet = new HashSet<>();  // 存放代理

    private static BeanFactory beanFactory = new BeanFactory();

    static {
        // 任务一：读取解析xml，通过反射技术实例化对象并且存储待用（map集合）
        // 加载xml
        InputStream resourceAsStream = BeanFactory.class.getClassLoader().getResourceAsStream("beans.xml");
        // 解析xml
        SAXReader saxReader = new SAXReader();
        try {
            Document document = saxReader.read(resourceAsStream);
            Element rootElement = document.getRootElement();

            //读取beans.xml下 配置的base-package属性，获得扫描包名
            List<Element> list  = rootElement.selectNodes("//component-scan");
            for (int i = 0; i < list .size(); i++) {
                Element element =  list .get(i);
                // 获取包的路径
                String basePackage = element.attributeValue("base-package");

                // 获得该配置包下的所有类的全限定名
                List<String> className = ComponentScanUtils.getClassName(basePackage);

                // 注册类到容器map
                // 处理@Service\@Transactional
                loadBean(className);

                // 处理被依赖的类
                dealAutowired();

                // 生成代理对象
                generateProxy();
            }


        } catch (Exception e) {
            e.printStackTrace();
        }

    }

    private static void loadBean(List<String> allClassName) throws Exception {
        for(String className : allClassName){
            Class<?> aClass = Class.forName(className);
            Set<String> autowiredBeans = new HashSet<>();
            //返回该class的注解
            Service annotation = aClass.getAnnotation(Service.class);
            if(annotation != null){
                if(!aClass.isAnnotation()){
                    //获取bean的名称id,value为空取类名首字母小写
                    String id = StringUtils.isNullOrEmpty(annotation.value()) ? aClass.getSimpleName().substring(0, 1).toLowerCase() + aClass.getSimpleName().substring(1) : annotation.value();
                    Object o = aClass.newInstance();
                    //加入容器map
                    map.put(id,o);
                    //获得该class所有声明方法
                    Field[] fields = aClass.getDeclaredFields();
                    for(Field field : fields){
                        //AccountDao
                        Autowired autowired = field.getAnnotation(Autowired.class);
                        if(autowired != null){
                            //因为有可能暂未得到被注入的类的实例化对象，所以先将被@Autowired注解标志的类缓存到一个map中
                            autowiredBeans.add(field.getName());
                        }
                    }

                    if(!autowiredBeans.isEmpty()){
                        //key为被依赖的bean id,value为依赖的bean name set
                        autowiredMap.put(id,autowiredBeans);
                    }

                    Method[] methods = aClass.getDeclaredMethods();
                    for(Method method : methods){
                        Transactional transactional = method.getAnnotation(Transactional.class);
                        if(transactional != null){
                            //只要有一个即创建该被依赖类的代理类
                            proxySet.add(id);
                        }
                    }

                }
            }
        }

    }

    private static void dealAutowired() {
        autowiredMap.forEach((key,value)->{
            for(String autowiredName :value){
                //根据key获取parent object
                Object parentObject = map.get(key);
                try {
                    wire(parentObject,autowiredName,map.get(autowiredName));
                    map.put(key,parentObject);
                } catch (Exception e) {
                    e.printStackTrace();
                }
            }
        });
    }

    /**
     * @param obj 要注入的对象
     * @param properName 属性名
     * @param value 属性值
     * @throws Exception
     */
    public static void wire(Object obj,String properName,Object value) throws Exception {
        //获取该类的类型
        Class<? extends Object> clazz = obj.getClass();
        // 获取对象的属性
        Field field = clazz.getDeclaredField(properName);
        //开启访问权限
        field.setAccessible(true);
        //为该属性注入值
        field.set(obj, value);
    }

    //生成代理对象
    private static void generateProxy() {
        if(proxySet.isEmpty()){
            return;
        }

        for(String needProxy : proxySet){
            //代理工厂
            ProxyFactory proxyFactory = (ProxyFactory)map.get("proxyFactory");
            //获取被代理的对象
            Object original = map.get(needProxy);
            Class<?>[] interfaces = original.getClass().getInterfaces();
            Object proxy;
            //  实现了接口使用jdk
            if (interfaces.length == 0) {
                proxy = proxyFactory.getJdkProxy(original);
                //  未实现接口，使用cglib代理
            } else {
                proxy = proxyFactory.getCglibProxy(original);
            }
            map.put(needProxy, proxy);
        }
    }
    // 任务二：对外提供获取实例对象的接口（根据id获取）
    public static  Object getBean(String id) {
        return map.get(id);
    }

}
